-- debug
local Image = require('src/Graphics/Image')
local Log = require('leviathan/Log')

-- loader for "tiled" map editor maps (.tmx,xml-based) http://www.mapeditor.org/
-- supports multiple layers
-- NOTE : function ReplaceMapTileClass (tx,ty,oldTileType,newTileType,fun_callback) end
-- NOTE : function TransmuteMap (from_to_table) end -- from_to_table[old]=new
-- NOTE : function GetMousePosOnMap () return gMouseX+gCamX-gScreenW/2,gMouseY+gCamY-gScreenH/2 end

local TiledLoader = {}

kMapTileTypeEmpty = 0
local floor = math.floor
local ceil = math.ceil

function TiledLoader:Load(filename)
--    local filepath = 'maps/'..filename
    local filepath = filename
    Log:debug('Loading map: '..filepath)
    if not love.filesystem.getInfo(filepath) then
        Log:error('Can not find map: '..filepath)
        return
    end
--    spritepath_removeold = "../"
--    spritepath_prefix = ""
--    gTileGfx = {}

    local tilesets, layers = TiledLoader:ParseMap(filepath)
    local tiles = TiledLoader:ParseTileset(tilesets)
--print_r(tiles)
    gMapLayers = layers
--print_r(layers)

--    for gid, tile in pairs(tiles) do
--        local path = tile.source
--        path = spritepath_prefix .. string.gsub(path,"^"..string.gsub(spritepath_removeold,"%.","%%."),"")
--        local raw = love.image.newImageData(path)
--        local w, h = raw:getWidth(),raw:getHeight()
--        for y=0, floor(h/gTileHeight)-1 do
--            for x=0, floor(w/gTileWidth)-1 do
--                local sprite = love.image.newImageData(gTileWidth, gTileHeight)
--                sprite:paste(raw, 0, 0, x*gTileWidth, y*gTileHeight, gTileWidth, gTileHeight)
--                gTileGfx[gid] = love.graphics.newImage(sprite)
--                gid = gid + 1
--            end
--        end
--    end
    return tiles, layers
end

function TiledLoader:GetMapTile(tx, ty, layerid) -- coords in tiles
    local row = gMapLayers[layerid][ty]
    return row and row[tx] or kMapTileTypeEmpty
end

--function TiledLoader:DrawNearCam(camx, camy)
--    camx,camy = floor(camx),floor(camy)
--    local screen_w = love.graphics.getWidth()
--    local screen_h = love.graphics.getHeight()
--    local minx,maxx = floor((camx-screen_w/2)/gTileWidth),ceil((camx+screen_w/2)/gTileWidth)
--    local miny,maxy = floor((camy-screen_h/2)/gTileHeight),ceil((camy+screen_h/2)/gTileHeight)
--    for z = 1,#gMapLayers do
--        for x = minx,maxx do
--            for y = miny,maxy do
--                local gfx = gTileGfx[TiledLoader:GetMapTile(x + 1, y + 1, z)]
--                if (gfx) then 
--                    local sx = x*gTileWidth - camx + screen_w/2
--                    local sy = y*gTileHeight - camy + screen_h/2
--                    love.graphics.draw(gfx,sx,sy) -- x, y, r, sx, sy, ox, oy
--                end
--            end
--        end
--    end
--end


-- ***** ***** ***** ***** ***** xml parser


-- LoadXML from http://lua-users.org/wiki/LuaXml
function TiledLoader:LoadXML(s)
    local function LoadXML_parseargs(s)
        local arg = {}
        string.gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a)
            arg[w] = a
        end)
        return arg
    end
    local stack = {}
    local top = {}
    table.insert(stack, top)
    local ni,c,label,xarg, empty
    local i, j = 1, 1
    while true do
        ni,j,c,label,xarg, empty = string.find(s, "<(%/?)([%w:]+)(.-)(%/?)>", i)
        if not ni then break end
        local text = string.sub(s, i, ni-1)
        if not string.find(text, "^%s*$") then
            table.insert(top, text)
        end
        if empty == "/" then  -- empty element tag
            table.insert(top, {label=label, xarg=LoadXML_parseargs(xarg), empty=1})
        elseif c == "" then   -- start tag
            top = {label=label, xarg=LoadXML_parseargs(xarg)}
            table.insert(stack, top)   -- new level
        else  -- end tag
            local toclose = table.remove(stack)  -- remove top
            top = stack[#stack]
            if #stack < 1 then
                error("nothing to close with "..label)
            end
            if toclose.label ~= label then
                error("trying to close "..toclose.label.." with "..label)
            end
            table.insert(top, toclose)
        end
        i = j+1
    end
    local text = string.sub(s, i)
    if not string.find(text, "^%s*$") then
        table.insert(stack[#stack], text)
    end
    if #stack > 1 then
        error("unclosed "..stack[stack.n].label)
    end
    return stack[1]
end


-- ***** ***** ***** ***** ***** parsing the tilemap xml file

local function getTilesets(node)
    local tiles = {}
    for k, sub in ipairs(node) do
        if (sub.label == "tileset") then
--            print(sub.xarg.source)
--            tiles[tonumber(sub.xarg.firstgid)] = sub[1].xarg.source -- 1.0 map version
            tiles[tonumber(sub.xarg.firstgid)] = sub.xarg.source -- 1.4 map version
        end
    end
    return tiles
end

--local function getLayers(gMapWidth, gMapHeight, node)
local function getLayers(node)
    local layers = {}
    -- parsing layers
    for k, sub in ipairs(node) do
        if (sub.label == "layer") then --  and sub.xarg.name == layer_name
            local layer = {}
            table.insert(layers, layer)
            width = tonumber(sub.xarg.width)
            -- creating layer array
            for i = 1, gMapWidth do
                layer[i] = {}
            end
            i = 1
            j = 1
            local function addTile(gidString)
                --if (j == 1) then
                --    layer[i] = {}
                --end
                layer[i][j] = tonumber(gidString)
--                j = j + 1
--                if j > width then
--                    j = 1
--                    i = i + 1
--                end
                i = i + 1
                if i > width then
                    i = 1
                    j = j + 1
                end
            end
            local data = sub[1]
            if data.xarg.encoding == "csv" then
                for l in string.gmatch(data[1], "([^,]+)") do
                    addTile(l)
                end
            else
                for l, child in ipairs(data) do
                    addTile(child.xarg.gid)
                end
            end
        end
    end
    return layers
end

function TiledLoader:ParseMap(filename)
    local xml = TiledLoader:LoadXML(love.filesystem.read(filename))
    gTileWidth, gTileHeight = 16, 16
    gMapWidth, gMapHeight = 0, 0
    for k, sub in ipairs(xml) do
        if sub.label == "map" then
            local version = tonumber(sub.xarg.version)
            if version < 1.4 then
                print('WARNING! Map version is less than 1.4! Possible errors ahead!')
            end
            gMapWidth = tonumber(sub.xarg.width)
            gMapHeight = tonumber(sub.xarg.height)
            gTileWidth = tonumber(sub.xarg.tilewidth)
            gTileHeight = tonumber(sub.xarg.tileheight)
            local tilesets = getTilesets(sub)
--            local layers = getLayers(gMapWidth, gMapHeight, sub)
            local layers = getLayers(sub)
            layers['mapWidth'] = gMapWidth
            layers['mapHeight'] = gMapHeight
            layers['mapTileWidth'] = gTileWidth
            layers['mapTileHeight'] = gTileHeight
            return tilesets, layers
        end
    end
    return nil, nil
end

function TiledLoader:ParseTileset(tilesets)
    local tiles = {}
    local tileoffsetx, tileoffsety
    for gid, tileset in pairs(tilesets) do
        local removeold = "../"
        local prefix = ""
        local path = prefix .. string.gsub(tileset,"^"..string.gsub(removeold,"%.","%%."),"")
        if love.filesystem.read(path) then
            Log:debug('Loading tileset: '..path)
        else
            Log:error('Failed loading tileset: '..path)
        end
        local xml = TiledLoader:LoadXML(love.filesystem.read(path))
        for k, sub in ipairs(xml) do
            if sub.label == 'tileset' then
                for id, node in ipairs(sub) do
                    -- checking tileoffset for tileset
                    if node.label == 'tileoffset' then
                        tileoffsetx = node.xarg.x
                        tileoffsety = node.xarg.y
                    end
                    -- parsing tiles
                    if (node.label == "tile") then
                        local source = node[1].xarg.source
                        local path = prefix .. string.gsub(source,"^"..string.gsub(removeold,"%.","%%."),"")
                        local realgid = tonumber(gid) + tonumber(node.xarg.id)
                        local tile = Image(path)

--                        tile.source = node[1].xarg.source

                        if tileoffsetx or tilesety then
                            tile.tileoffsetx = tileoffsetx
                            tile.tileoffsety = tileoffsety
                            tileoffsetx = nil
                            tileoffsety = nil
                        end
                        tiles[realgid] = tile
                    end
                end
            end
        end
    end
    return tiles
end

return TiledLoader
